package pentago.main;

import java.awt.Point;
import java.util.ArrayList;
import java.util.List;

import org.newdawn.slick.AppGameContainer;
import org.newdawn.slick.BasicGame;
import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;

import pentago.engine.BoardPlate;
import pentago.engine.ClickableObject;
import pentago.engine.ConfirmButton;
import pentago.engine.GameBoard;
import pentago.engine.GameEngine;
import pentago.engine.GameOptions;
import pentago.engine.Move;
import pentago.engine.RotationArrow;
import pentago.exceptions.PentagoInvalidGameStateException;
import pentago.exceptions.PentagoInvalidMarblePositionException;
import pentago.exceptions.PentagoInvalidMoveException;
import pentago.exceptions.PentagoInvalidPlatePositionException;
import pentago.exceptions.PentagoUnknownPlayerException;

public class GameScreen extends BasicGame {

    private static int WIDTH = 1024;
    private static int HEIGHT = 800;

    private GameOptions gameOptions;

    private GameEngine gameEngine;
    private GameBoard gameBoard;
    private Color plateColor = Color.white;
    private Color arrowColor = Color.red;
    private Color clickedArrowColor = Color.red;
    private Color firstPlayerColor = Color.blue;
    private Color secondPlayerColor = Color.yellow;
    private Color confirmColor = Color.green;

    private float scale;

    private int leftBorderShift;// (int) (WIDTH * 0.1);
    private int topBorderShift;// (int) (HEIGHT * 0.1);
    private int plateSpacing = 0;
    private int xPos;
    private int yPos;
    private int statusXPos;
    private int statusYPos;

    private byte winner;

    private List<ClickableObject> clicableObjects = new ArrayList<ClickableObject>();

    public GameScreen() {
	super("Pentago");
    }

    /**
     * @param gameOptions
     * @throws SlickException
     */
    public void startGame(GameOptions gameOptions) throws SlickException {

	this.gameOptions = gameOptions;
	readGameOptions(gameOptions);

	AppGameContainer app = new AppGameContainer(this);

	app.setDisplayMode(WIDTH, HEIGHT, false);
	app.start();
    }

    @Override
    public void render(GameContainer container, Graphics g)
	    throws SlickException {

	try {
	    if (gameEngine.getGameState() != GameEngine.IN_PROGRESS
		    || gameOptions.isDisplayMoves())
		drawObjects(g);
	    switch (winner) {
	    case GameEngine.FIRST_WON:
		g.drawString(gameOptions.getFirstPlayerName() + " has won!",
			statusXPos, statusYPos);
		break;
	    case GameEngine.SECOND_WON:
		g.drawString(gameOptions.getSecondPlayerName() + " has won!",
			statusXPos, statusYPos);
		break;
	    case GameEngine.TIE:
		g.drawString("TIE!", statusXPos, statusYPos);
		break;
	    }
	} catch (PentagoInvalidPlatePositionException e) {
	    throw new SlickException(e.toString());
	} catch (PentagoInvalidMarblePositionException e) {
	    throw new SlickException(e.toString());
	}
    }

    @Override
    public void init(GameContainer container) throws SlickException {

	Image plateImage = new Image("img/plate.png");
	Image marbleImage = new Image("img/circle.png");
	Image clockwiseArrowImg = new Image("img/arrow_clockwise.png");
	Image counterclockwiseArrowImg = new Image(
		"img/arrow_counterclockwise.png");
	Image confirmButtonImg = new Image("img/confirm2.png");

	int minWindowHeight = 2
		* (plateImage.getHeight() + counterclockwiseArrowImg
			.getHeight()) + confirmButtonImg.getHeight();
	int minWindowWidth = 2 * (plateImage.getWidth() + counterclockwiseArrowImg
		.getHeight());
	scale = Math.min((float) WIDTH / minWindowWidth, (float) HEIGHT
		/ minWindowHeight);

	topBorderShift = (int) (scale * counterclockwiseArrowImg.getHeight());
	leftBorderShift = (int) (scale * counterclockwiseArrowImg.getHeight());

	statusXPos = leftBorderShift;
	statusXPos += 2 * clockwiseArrowImg.getHeight() * scale;
	statusXPos += 2 * plateImage.getWidth() * scale;
	statusXPos += plateSpacing;

	statusYPos = topBorderShift;
	statusYPos += clockwiseArrowImg.getHeight() * scale;
	statusYPos += plateImage.getHeight() * scale;

	gameBoard = GameBoard.getInstance(plateImage, marbleImage, scale,
		gameOptions.getBoardState());

	try {
	    int firstDepth = gameOptions.getFirstPlayerDepth();
	    int secondDepth = gameOptions.getSecondPlayerDepth();
	    gameEngine = new GameEngine(gameBoard);
	    gameEngine.setFirstPlayerMode(gameOptions.getFirstPlayerMode(),
		    firstDepth);
	    gameEngine.setMaxRunTimeFirst(gameOptions.getMaxRunTime());
	    gameEngine.setSecondPlayerMode(gameOptions.getSecondPlayerMode(),
		    secondDepth);
	    gameEngine.setMaxRunTimeSecond(gameOptions.getMaxRunTime());
	    winner = GameEngine.NO_PLAYER;
	    gameEngine.beginGame();
	} catch (PentagoInvalidMoveException e) {
	    e.printStackTrace();
	} catch (PentagoUnknownPlayerException e) {
	    e.printStackTrace();
	} catch (PentagoInvalidGameStateException e) {
	    e.printStackTrace();
	}

	int shiftY = 0;
	int shiftX = 0;

	Point objectCoordinates;
	RotationArrow rotationArrow;
	RotationArrow.enableColoring();

	try {
	    this.clicableObjects
		    .add(new ConfirmButton(
			    gameEngine,
			    (int) (scale * (counterclockwiseArrowImg
				    .getHeight() + plateImage.getWidth() - confirmButtonImg
				    .getWidth() / 1.55)),
			    (int) (scale * (plateImage.getHeight() * 2
				    + counterclockwiseArrowImg.getHeight() - 0.6 * confirmButtonImg
				    .getHeight())), confirmButtonImg, scale));

	    objectCoordinates = gameBoard
		    .getPlatePixelCoordinates(GameBoard.UPPER_LEFT);
	    this.clicableObjects.add(new BoardPlate(gameEngine,
		    objectCoordinates.x, objectCoordinates.y, plateImage,
		    scale, GameBoard.UPPER_LEFT));

	    shiftX = (int) (scale * clockwiseArrowImg.getHeight());
	    rotationArrow = new RotationArrow(gameEngine, objectCoordinates.x
		    - shiftX, objectCoordinates.y + shiftY, clockwiseArrowImg,
		    scale, GameBoard.UPPER_LEFT, Move.CLOCKWISE);
	    rotationArrow.setClickedColor(clickedArrowColor);
	    rotationArrow.setNotClickedColor(arrowColor);

	    shiftY = (int) (scale * counterclockwiseArrowImg.getHeight());
	    this.clicableObjects.add(rotationArrow);
	    rotationArrow = new RotationArrow(gameEngine, objectCoordinates.x,
		    objectCoordinates.y - shiftY, counterclockwiseArrowImg,
		    scale, GameBoard.UPPER_LEFT, Move.COUNTERCLOCKWISE);
	    rotationArrow.setClickedColor(clickedArrowColor);
	    rotationArrow.setNotClickedColor(arrowColor);
	    this.clicableObjects.add(rotationArrow);

	    objectCoordinates = gameBoard
		    .getPlatePixelCoordinates(GameBoard.UPPER_RIGHT);
	    this.clicableObjects.add(new BoardPlate(gameEngine,
		    objectCoordinates.x, objectCoordinates.y, plateImage,
		    scale, GameBoard.UPPER_RIGHT));

	    shiftX = (int) (scale * (plateImage.getWidth() - clockwiseArrowImg
		    .getWidth()));
	    shiftY = (int) (scale * clockwiseArrowImg.getHeight());
	    rotationArrow = new RotationArrow(gameEngine, objectCoordinates.x
		    + shiftX, objectCoordinates.y - shiftY, clockwiseArrowImg,
		    scale, GameBoard.UPPER_RIGHT, Move.CLOCKWISE);
	    rotationArrow.setClickedColor(clickedArrowColor);
	    rotationArrow.setNotClickedColor(arrowColor);

	    shiftX = (int) (scale * plateImage.getWidth());
	    this.clicableObjects.add(rotationArrow);
	    rotationArrow = new RotationArrow(gameEngine, objectCoordinates.x
		    + shiftX, objectCoordinates.y, counterclockwiseArrowImg,
		    scale, GameBoard.UPPER_RIGHT, Move.COUNTERCLOCKWISE);
	    rotationArrow.setClickedColor(clickedArrowColor);
	    rotationArrow.setNotClickedColor(arrowColor);
	    this.clicableObjects.add(rotationArrow);

	    objectCoordinates = gameBoard
		    .getPlatePixelCoordinates(GameBoard.LOWER_LEFT);
	    this.clicableObjects.add(new BoardPlate(gameEngine,
		    objectCoordinates.x, objectCoordinates.y, plateImage,
		    scale, GameBoard.LOWER_LEFT));

	    shiftY = (int) (scale * plateImage.getHeight());
	    rotationArrow = new RotationArrow(gameEngine, objectCoordinates.x,
		    objectCoordinates.y + shiftY, clockwiseArrowImg, scale,
		    GameBoard.LOWER_LEFT, Move.CLOCKWISE);
	    rotationArrow.setClickedColor(clickedArrowColor);
	    rotationArrow.setNotClickedColor(arrowColor);
	    this.clicableObjects.add(rotationArrow);

	    shiftX = (int) (scale * counterclockwiseArrowImg.getHeight());
	    shiftY = (int) (scale * (plateImage.getHeight() - counterclockwiseArrowImg
		    .getWidth()));
	    rotationArrow = new RotationArrow(gameEngine, objectCoordinates.x
		    - shiftX, objectCoordinates.y + shiftY,
		    counterclockwiseArrowImg, scale, GameBoard.LOWER_LEFT,
		    Move.COUNTERCLOCKWISE);
	    rotationArrow.setClickedColor(clickedArrowColor);
	    rotationArrow.setNotClickedColor(arrowColor);
	    this.clicableObjects.add(rotationArrow);

	    objectCoordinates = gameBoard
		    .getPlatePixelCoordinates(GameBoard.LOWER_RIGHT);
	    this.clicableObjects.add(new BoardPlate(gameEngine,
		    objectCoordinates.x, objectCoordinates.y, plateImage,
		    scale, GameBoard.LOWER_RIGHT));

	    shiftX = (int) (scale * plateImage.getWidth());
	    shiftY = (int) (scale * (plateImage.getHeight() - clockwiseArrowImg
		    .getWidth()));
	    rotationArrow = new RotationArrow(gameEngine, objectCoordinates.x
		    + shiftX, objectCoordinates.y + shiftY, clockwiseArrowImg,
		    scale, GameBoard.LOWER_RIGHT, Move.CLOCKWISE);
	    rotationArrow.setClickedColor(clickedArrowColor);
	    rotationArrow.setNotClickedColor(arrowColor);
	    this.clicableObjects.add(rotationArrow);

	    shiftX = (int) (scale * (plateImage.getWidth() - counterclockwiseArrowImg
		    .getWidth()));
	    shiftY = (int) (scale * plateImage.getHeight());
	    rotationArrow = new RotationArrow(gameEngine, objectCoordinates.x
		    + shiftX, objectCoordinates.y + shiftY,
		    counterclockwiseArrowImg, scale, GameBoard.LOWER_RIGHT,
		    Move.COUNTERCLOCKWISE);
	    rotationArrow.setClickedColor(clickedArrowColor);
	    rotationArrow.setNotClickedColor(arrowColor);
	    this.clicableObjects.add(rotationArrow);
	} catch (PentagoInvalidPlatePositionException e) {
	    e.printStackTrace();
	}
    }

    @Override
    public void update(GameContainer container, int delta)
	    throws SlickException {
	if (gameEngine.getGameState() != GameEngine.IN_PROGRESS) {
	    RotationArrow.disableColoring();
	    winner = gameEngine.getGameState();
	    gameEngine.endGame();
	    return;
	} else if (gameEngine.currentPlayerHuman()) {
	    Input input = container.getInput();
	    if (input.isMousePressed(Input.MOUSE_LEFT_BUTTON)) {
		xPos = input.getAbsoluteMouseX() - leftBorderShift;
		yPos = input.getAbsoluteMouseY() - topBorderShift;
		for (ClickableObject co : this.clicableObjects) {
		    co.executeClick(xPos, yPos, Input.MOUSE_LEFT_BUTTON);
		}
	    }
	} else {
	    // Input input = container.getInput();
	    // if (input.isMousePressed(Input.MOUSE_LEFT_BUTTON)) {
	    gameEngine.executeNextComputerMove();
	    // }
	}
    }

    @Override
    public boolean isAcceptingInput() {
	return true;
    }

    private void drawObjects(Graphics g)
	    throws PentagoInvalidMarblePositionException,
	    PentagoInvalidPlatePositionException {
	for (ClickableObject co : clicableObjects) {
	    if (co instanceof BoardPlate) {
		drawPlate(((BoardPlate) co).getPlatePosition());
	    } else if (co instanceof RotationArrow) {
		drawArrow((RotationArrow) co);
	    } else if (co instanceof ConfirmButton) {
		drawConfirmButton((ConfirmButton) co);
	    }
	}
	drawMarbles();
	drawStatus(g);
    }

    private void drawStatus(Graphics g) {
	if (gameEngine.getGameState() == GameEngine.IN_PROGRESS) {
	    if (gameEngine.getCurrentPlayer() == GameEngine.FIRST_PLAYER) {
		g.drawString(
			"Current player: " + gameOptions.getFirstPlayerName(),
			statusXPos, statusYPos);
	    } else {
		g.drawString(
			"Current player: " + gameOptions.getSecondPlayerName(),
			statusXPos, statusYPos);
	    }
	}
    }

    private void drawConfirmButton(ConfirmButton co) {
	co.getImg().draw(co.getX() + leftBorderShift,
		co.getY() + topBorderShift, scale, confirmColor);
    }

    private void drawArrow(RotationArrow co) {
	Image arrowImage = co.getImg();

	int xPos = leftBorderShift;
	int yPos = topBorderShift;

	xPos += co.getX();
	yPos += co.getY();
	arrowImage.draw(xPos, yPos, scale, co.getColor());
    }

    private void drawMarbles() throws PentagoInvalidMarblePositionException {
	byte platePosition;
	byte boardState[][] = gameBoard.getBoardState();
	int xPos;
	int yPos;
	Point marbleTablePosition;
	Point marbleCoordinates;
	Color playerMarbleColor;

	for (int y = 0; y < 6; y++)
	    for (int x = 0; x < 6; x++) {
		if (boardState[x][y] == GameEngine.NO_PLAYER)
		    continue;
		marbleTablePosition = new Point(x, y);
		playerMarbleColor = boardState[x][y] == GameEngine.FIRST_PLAYER ? firstPlayerColor
			: secondPlayerColor;
		xPos = leftBorderShift;
		yPos = topBorderShift;
		marbleCoordinates = gameBoard
			.getMarblePixelCoordinates(marbleTablePosition);
		xPos += marbleCoordinates.x;
		yPos += marbleCoordinates.y;
		platePosition = gameBoard
			.getPlateFromMarbleTablePosition(marbleTablePosition);
		if (platePosition == GameBoard.UPPER_RIGHT)
		    xPos += plateSpacing;
		if (platePosition == GameBoard.LOWER_LEFT)
		    yPos += plateSpacing;
		if (platePosition == GameBoard.LOWER_RIGHT) {
		    xPos += plateSpacing;
		    yPos += plateSpacing;
		}
		gameBoard.getMarbleImage().draw(xPos, yPos, scale,
			playerMarbleColor);
	    }
    }

    private void drawPlate(byte platePosition)
	    throws PentagoInvalidPlatePositionException {
	Point plateCoordinates;
	int xPos;
	int yPos;
	xPos = leftBorderShift;
	yPos = topBorderShift;
	plateCoordinates = gameBoard.getPlatePixelCoordinates(platePosition);
	xPos += plateCoordinates.getX();
	yPos += plateCoordinates.getY();
	if (platePosition == GameBoard.UPPER_RIGHT)
	    xPos += plateSpacing;
	if (platePosition == GameBoard.LOWER_LEFT)
	    yPos += plateSpacing;
	if (platePosition == GameBoard.LOWER_RIGHT) {
	    xPos += plateSpacing;
	    yPos += plateSpacing;
	}
	gameBoard.getPlateImage().draw(xPos, yPos, scale, plateColor);
    }

    private void readGameOptions(GameOptions options) {
	this.firstPlayerColor = options.getFirstPlayerColor();
	this.secondPlayerColor = options.getSecondPlayerColor();
	this.plateColor = options.getPlateColor();
    }

}
